package crap;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Event;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.image.MemoryImageSource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JToggleButton;

import tools.Arrow;


public class FluidHack {

	static int W = 384*2/3, H = 256*2/3;
	
	static float[] curPaint = new float[W*H], prevPaint = new float[W*H];
	
	static float[] vx = new float[W*H], prevVx = new float[W*H];
	static float[] vy = new float[W*H], prevVy = new float[W*H];

	static float[] ux = new float[W*H];
	static float[] uy = new float[W*H];

	static float[] div = new float[W*H];
	static float[] pressure = new float[W*H];
	static float[] prezzure = new float[W*H];
	
	
	static float[] pressureGradientX = new float[W*H];
	static float[] pressureGradientY = new float[W*H];
	

	static float[] vorticity = new float[W*H];
	static float[] uorticity = new float[W*H];
	
	static boolean pause = false;
	
	static int mode = 0;
	
	public static int V(Integer i) {
		return ((i==null)?0:i);
	}

	public static float V(Float i) {
		return ((i==null)?0f:i);
	}
	
	public static void main(String args[]) {
		
		int pixels[] = new int[W*H];
		final int R = 12,R_SQUARED=R*R;
		final int M = 20;
		
		final MemoryImageSource mis = new MemoryImageSource(W,H,pixels,0,W);
		mis.setAnimated(true);
		
		
		
//
//		HashMap<Pair<Integer,Integer>,Float> from = new HashMap<Pair<Integer,Integer>, Float>();
//		from.put(new Pair<Integer,Integer>(0,0),1f);
//		
//		HashMap<Pair<Integer,Integer>,Float> to = new HashMap<Pair<Integer,Integer>, Float>();
//
//		
//		float d = 1/4f;
//		for (int i=0;i<4;i++,d/=4f) {
//			
//			to.clear();
//			
//			for (Entry<Pair<Integer,Integer>,Float> xs: from.entrySet()) {
//				
//				Pair<Integer,Integer> target = xs.getKey();
//				Pair<Integer,Integer> 
//					left = new Pair<Integer,Integer>(target.a-1,target.b), 
//					right = new Pair<Integer,Integer>(target.a+1,target.b),
//					bottom = new Pair<Integer,Integer>(target.a,target.b-1),
//					up = new Pair<Integer,Integer>(target.a,target.b+1);
//				
//				to.put(left, (V(to.get(left))+1)*xs.getValue() );
//				to.put(right, (V(to.get(right))+1)*xs.getValue() );
//				to.put(bottom, (V(to.get(bottom))+1)*xs.getValue() );
//				to.put(up, (V(to.get(up))+1)*xs.getValue() );
//				
//			}
//			
//			HashMap<Pair<Integer,Integer>,Float> t = from;
//			from = to;
//			to = t;
//			
//		}
//		
//		for (int i=-5;i<=5;i++) {
//			for (int j=-5;j<=5;j++) {
//				
//				Float f = from.get(new Pair<Integer,Integer>(i,j));
//				System.out.print(V(f)*d+"\t");
//				
//			}
//			System.out.println();
//		}
//		if (true)
//			return;
		
		
		
		
		
		
		
		
		
		
		

		JFrame f = new JFrame();
		
		f.addKeyListener(new KeyAdapter(){
			@Override
			public void keyPressed(KeyEvent e) {
				if (e.getKeyChar()!=' ')
					mode++;
				else
					synchronized (FluidHack.class) {
						pause = !pause;
						FluidHack.class.notify();
					}
//				System.out.println(e);
			}
		});
		
		JComponent c = new JComponent() {
			{ setPreferredSize(new Dimension(W*2,H*2)); }
			Image offscreen = createImage(mis);
			
			
			Line2D a2 = new Line2D.Double();
			
			protected void paintComponent(Graphics g) {
				
				synchronized (FluidHack.class) {
					g.drawImage(offscreen, 0, 0, getWidth(), getHeight(), 0, 0, W, H, this);
	
					
					g.setColor(Color.GREEN);
					double scaleX = (double)getWidth()/W, scaleY = (double)getHeight()/H;
					
//					((Graphics2D)g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
//					((Graphics2D)g).setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
					
					
					int k = 5;
					double vs = 10;
					double ps = 10;
					
//					
//					g.setColor(Color.WHITE);
//					for (int y=M,o=W*y+M;y<H-M;y+=k,o+=k*W+M+M)
//						for (int x=M;x<W-M;x+=k,o+=k) {
//							a2.setLine(x*scaleX, y*scaleY, x*scaleX+pressureGradientX[x+y*W]*ps, y*scaleY+pressureGradientY[x+y*W]*ps);
//							((Graphics2D)g).draw(a2);
//						}
////	
//					g.setColor(Color.GREEN);
//					
//					for (int y=M,o=W*y+M;y<H-M;y+=k,o+=k*W+M+M)
//						for (int x=M;x<W-M;x+=k,o+=k) {
//							a2.setLine(x*scaleX, y*scaleY, x*scaleX+vx[x+y*W]*vs, y*scaleY+vy[x+y*W]*vs);
//							((Graphics2D)g).draw(a2);
//						}
					

//					g.setColor(Color.MAGENTA);
//					for (int y=M,o=W*y+M;y<H-M;y+=k,o+=k*W+M+M)
//						for (int x=M;x<W-M;x+=k,o+=k) {
//							a2.setLine(x*scaleX, y*scaleY, x*scaleX+ux[x+y*W]*vs, y*scaleY+uy[x+y*W]*vs);
//							((Graphics2D)g).draw(a2);
//						}
						
				}
				/*
				for (int y=M;y<H-M;y+=4)
					for (int x=M;x<W-M;x+=4) {
						
						a2.setLine(x*scaleX, y*scaleY, x*scaleX, y*scaleY+1);
						
						((Graphics2D)g).draw(a2);
						
					}
				*/	
				
			}
		};

		f.getContentPane().add( c );
		
		f.setLocation(100,100);
		f.pack();
		f.setSize(new Dimension(f.getWidth()*3,f.getHeight()*3));
		f.setVisible(true);
		
		f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		


		final JToggleButton clickButton = new JToggleButton();
		final JToggleButton stirButton = new JToggleButton();
		
		
		final Point mouse = new Point(), prevMouse = new Point();
		
		MouseAdapter ma = new MouseAdapter() {
			@Override
			public void mouseReleased(MouseEvent e) {
				prevMouse.setLocation(e.getPoint());
				mouse.setLocation(e.getPoint());
				
				clickButton.setSelected(false);
				stirButton.setSelected(false);
			}
			
			@Override
			public void mousePressed(MouseEvent e) {
				prevMouse.setLocation(e.getPoint());
				mouse.setLocation(e.getPoint());
				
				clickButton.setSelected(false);
				stirButton.setSelected(false);
			}
			
			@Override
			public void mouseDragged(MouseEvent e) {
				prevMouse.setLocation(mouse);
				mouse.setLocation(e.getPoint());
				clickButton.setSelected(true);

				stirButton.setSelected((e.getModifiers()&MouseEvent.BUTTON1_MASK)==0);
			}
			
			
		};
		
		c.addMouseMotionListener(ma);
		c.addMouseListener(ma);

		
		final float centerW = 1.5f, sumW = 4.f+centerW;
		
		double lastTime = System.nanoTime()/1e9;
		
		while (true) try {
//			
//			if (pause) 
//				synchronized(FluidHack.class) {
//					FluidHack.class.wait();
//				}
			
			//advection
			
			
			//diffusion
			
			
			synchronized (FluidHack.class) {
				
				float[] tempPaint = curPaint;
				curPaint = prevPaint;
				prevPaint = tempPaint;
				
				float[] tempVx = vx;
				vx = prevVx;
				prevVx = tempVx;
				
				float[] tempVy = vy;
				vy = prevVy;
				prevVy = tempVy;
				
				
				//color advect
				//velocity advect
				float s = pause?0:1;
					
				for (int y=M,o=W*y+M;y<H-M;y++,o+=M+M)
					for (int x=M;x<W-M;x++,o++) {
						
						float dx = prevVx[o]*s, dy = prevVy[o]*s;
						
						curPaint[o] = bilerp(x-dx, y-dy, prevPaint, W, H);
						
						vx[o] = bilerp(x-dx,y-dy, prevVx, W, H);
						vy[o] = bilerp(x-dx,y-dy, prevVy, W, H);
						
//						uorticity[o] = bilerp(x-dx, y-dy, vorticity, W, H);
					}

				

				//forces / manual

				float mx = (mouse.x-prevMouse.x), my = (mouse.y-prevMouse.y);
				
				float paint = stirButton.isSelected()?0:1f;
				boolean duese = stirButton.isSelected(); 

				double sqSigma = R_SQUARED;
				
				double currentTime = System.nanoTime()/1e9;
				float dt = (float)(currentTime-lastTime);
				lastTime = currentTime;
				
				float mouseSpeedX = 0.01f*mx/dt, mouseSpeedY = 0.01f*my / dt;
				
				
				if (clickButton.isSelected())
				for (int y=M,o=M*W+M, w= c.getWidth(), h=c.getHeight();y<H-M;y++,o+=M+M)
					for (int x=M;x<W-M;x++,o++) {
		
						final int dx = (x-mouse.x*W/w), dy = (y-mouse.y*H/h);
						final int rSq = dx*dx+dy*dy;
						
						if (rSq>R_SQUARED) 
							continue;
						
//						float normR = (float)(Math.sqrt(rSq)/Math.sqrt(R_SQUARED));
						
						double t = rSq;
						
						float force = (float)Math.exp(-0.5*t*t/sqSigma);
						float oor = 1.0f-force;
						
						if (stirButton.isSelected()) {
							vx[o] = mouseSpeedX*force;
							vy[o] = mouseSpeedY*force;
//							vx[o] = vx[o]*oor+mouseSpeedX*force;
//							vy[o] = vy[o]*oor+mouseSpeedY*force;
						} else 
							curPaint[o] = Math.min( curPaint[o]+paint*force, 1f );

					}
				
				
				

				//viscous diffuse
//				for (int i=0;i<10;i++) {
//					float tx[] = vx;
//					vx = prevVx;
//					prevVx = tx;
//					
//					float ty[] = vy;
//					vy = prevVy;
//					prevVy = ty;
//	
//					float tp[] = curPaint;
//					tp = prevPaint;
//					curPaint = tp;
//					
//
//					for (int y=M,o=W*y+M;y<H-M;y++,o+=M+M)
//						for (int x=M;x<W-M;x++,o++) {
//	
//							vx[o] = (prevVx[o-1]+prevVx[o+1]+prevVx[o-W]+prevVx[o+W]+prevVx[o]*centerW)/sumW;
//							vy[o] = (prevVy[o-1]+prevVy[o+1]+prevVy[o-W]+prevVy[o+W]+prevVy[o]*centerW)/sumW;
//							
////							uorticity[o] = (vorticity[o-1]+vorticity[o+1]+vorticity[o-W]+vorticity[o+W]+vorticity[o]*centerW)/sumW;
//							
//	//						curPaint[o] = (prevPaint[o-1]+prevPaint[o+1]+prevPaint[o-W]+prevPaint[o+W]+prevPaint[o]*centerW)/sumW;
//						}
//				}
				

				
				
//				pressure / divergence
				for (int y=M,o=W*y+M;y<H-M;y++,o+=M+M)
					for (int x=M;x<W-M;x++,o++) {
						div[o] = ((vx[o+1]-vx[o-1])+(vy[o+W]-vy[o-W]))*-0.5f;
						pressure[o] = prezzure[o] = 0;
					}

				
				
				for (int i=0;i<80;i++) {
					float[] t = pressure;
					pressure = prezzure;
					prezzure = t;
					
					for (int y=M,o=W*y+M;y<H-M;y++,o+=M+M)
						for (int x=M;x<W-M;x++,o++) {
							pressure[o] = (prezzure[o-1]+prezzure[o+1]+prezzure[o-W]+prezzure[o+W]+prezzure[o]+div[o])/5.0f;
						}
					
				}
				
				
				
				float k = -0.5f;
				for (int y=M,o=W*y+M;y<H-M;y++,o+=M+M)
					for (int x=M;x<W-M;x++,o++) {
						vx[o] += (pressure[o+1]-pressure[o-1])*k;
						vy[o] += (pressure[o+W]-pressure[o-W])*k;
					}
				
//				//wand
//				for (int x=M,o=M+W*M-W, O=(H-M-1)*W+M+W;x<W-M;x++,o++,O++) {
//					pressure[o] = pressure[o+W];
//					pressure[O] = pressure[O-W];
//				}
//				
//	
//				for (int y=M,o=M+W*M-1, O=W*M+W-1-M+1;y<H-M;y++,o+=W,O+=W) {
//					pressure[o] = pressure[o+1];
//					pressure[O] = pressure[O-1];
//				}
				

				
				//grad subtraction
//				float k = -22.0f;
//				for (int y=M,o=W*y+M;y<H-M;y++,o+=M+M)
//					for (int x=M;x<W-M;x++,o++) {
//						
//						pressureGradientX[o] = (pressure[o+1]-pressure[o-1])*k;
//						pressureGradientY[o] = (pressure[o+W]-pressure[o-W])*k;
//						
//						ux[o] = vx[o]+pressureGradientX[o];// + vy[o]*uorticity[o]*0.1f;
//						uy[o] = vy[o]+pressureGradientY[o];// - vx[o]*uorticity[o]*0.1f;
//						
////						vorticity[o] = (uy[o+1]-uy[o-1]) - (ux[o+W]-ux[o-W]); 
//					}
				
				
	
				

				
				
				// inject paint && apply force
				
//			
//			
//			
//			float k = 1.0f;
//			
//			for (int y=M,o=W*y+M;y<H-M;y++,o+=M+M)
//				for (int x=M;x<W-M;x++,o++) {
//					vx[o] += (pressure[o+1]-pressure[o-1])*k;
//					vy[o] += (pressure[o+W]-pressure[o-W])*k;
//				}
//		
//			
//			
//			

			
//			
//
//		
			/*
			 * 
			for (int y=1,o=W+1;y<H-1;y++,o+=2)
				for (int x=1;x<W-1;x++,o++)
					curPaint[o] = (prevPaint[o-1]+prevPaint[o+1]+prevPaint[o-W]+prevPaint[o+W])/4.0f;
			*/	

//			for (int y=M,o=W*y+M;y<H-M;y++,o+=M+M)
//				for (int x=M;x<W-M;x++,o++)
						
				for (int y=0,o=W*y;y<H;y++)
					for (int x=0;x<W;x++,o++)
						if (mode%2==0)
							pixels[o] = Y2RGBA(curPaint[o]);
						else
							pixels[o] = Y2RGBA(pressure[o]);
				
				
				
			}
			
			
			Thread.sleep(10);
			mis.newPixels();
			c.repaint();
			
		} catch (Exception ex) 
		{
			
			System.out.println(ex);;
			for (int y=M,o=W*y+M;y<H-M;y++,o+=M+M)
				for (int x=M;x<W-M;x++,o++) {
					
					vx[o] = prevVx[o] = 0;
					vy[o] = prevVy[o] = 0;
					
				}
			
			
//			break;
		}
		/**/
		
	}
	
	
	
	static float bilerp(float x, float y,float[] src, final int W, final int H ) {
		final int X = (int) x, Y = (int) y;
		final float rx = x-X, ry = y-Y;
		
		final int o = X+Y*W;
		final float ul = src[o], ur = src[o+1];
		final float ll = src[o+W], lr = src[o+1+W];
		
		final float u = ur*rx+ul*(1-rx);
		final float l = lr*rx+ll*(1-rx);
		
		return l*ry+u*(1-ry);
	}

	
	static int Y2RGBA(final float intensity) {
		
		final int r = clamp(0,255,(int) (255f*intensity));
		final int b = -clamp(-255,0,(int) (255f*intensity));
		
//		final int i = clamp(0,255,(int) (255f*intensity));
		
		return 0xFF000000 | b | (0<<8) | (r<<16);
		
		
	}
	
	static int clamp(final int byteIntensity) { return clamp(0,255,byteIntensity); }
	
	static int clamp(final int min, final int max, final int v) {
		final int a = (min>v?min:v);
		return (max<a?max:a);
	}
	
}



